<?php

/*
	MagicObject is an encapsulation class that use PHP 5 magic methods, has 2 main purposes :
	- polymorphism : call many methods from multiples classes
	- getter attribute access : call a getAttribute method when using $obj->attribute
*/
class MagicObject {
	protected $objects;
	protected static $AUTO_ENCAPSULATION = false;
	
	public function __construct($obj = null) {
		$this->objects = array($this);
		if ($obj != null) {
			if (is_array($obj)) {
				$this->objects = array_merge($this->objects, $obj);
			} else {
				$this->objects[] = $obj;
			}
		}
	}
	
	public function getObjects () {
		return $this->objects;
	}
	
	public function __call($name, $arguments) {
		foreach ($this->objects as $obj) {
			if (method_exists($obj, $name)) {
					return $this->autoencapsulate(call_user_func_array(array(&$obj, $name), $arguments));
					
			} else {
				list($found, $res) = $this->parseGetMethod($obj, $name);
				if ($found) return $res;
			}
		}
		
		throw new Exception( " Call method ". $name. " not found");
	}
	
	public function __get($name) {
		assert(is_array($this->objects)) or die ('$this->objects not initialized ! maybe forgot to call constructor ?');
		
		foreach ($this->objects as $obj) {
			
			$method = 'get'.strtoupper($name[0]).substr($name, 1);
			if (method_exists($obj, $name)) {
				return $this->autoencapsulate($obj->$name());
				
			} else if (method_exists($obj, $method)) {
				return $this->autoencapsulate($obj->$method());
				
			} else if (isset($obj->$name)) {
				return $this->autoencapsulate($obj->$name);
			} else {
				list($found, $res) = $this->parseGetMethod($obj, $name);
				if ($found) return $res;
			}
		}
		
		$classes = array();
		foreach($this->objects as $obj) {
			$classes[] = get_class($obj);
		}
		
		throw new Exception( " Getter/Attribute ". $name. " not found in ". join(', ', $classes));
	}
	
	protected function autoencapsulate ($obj) {
		$AUTO_ENCAPSULATION = eval('return '.get_class($this).'::$AUTO_ENCAPSULATION;');
		
		if ($AUTO_ENCAPSULATION != false && self::$AUTO_ENCAPSULATION)
			return $this->encapsulate($obj, $class);
		return $obj;
	}
	
	protected function encapsulate ($obj, $class = null) {
		if ($class == null)
			$class = array('*' => get_class($this));
		foreach ($class as $o => $target) {
			if (is_object($obj) && (
				($o == '*' && !($obj instanceof $class))
					|| $obj instanceof $o
					|| in_array($o, class_implements($obj))
			)
			) {
				return new $target($obj);
			
			} else if (is_array($obj)) {
				foreach ($obj as $k => $v) {
					$obj[$k] = $this->encapsulate($v, $class);
				}
			}
		}
		
		return $obj;
	}
	
	private function parseGetMethod ($obj, $name) {
		$matches = array();
		if (preg_match('/^get(.+)$/', $name, $matches)) {
			$attname = strtolower($matches[1][0]).substr($matches[1], 1);
			if (isset($obj->$attname)) {
				return array(true, $obj->$attname);
			}
		}
		return array(false, null);
	}
}
